from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect, get_object_or_404
from itertools import chain
from django.template import RequestContext
from django.db.models import Q
from question.models import Question, Difficulty, Correct, Wrong, Answer
from page.models import Page
from test.models import Test, Contains
from question.question_type import QUESTION_TYPES
from django.contrib import messages
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.core.exceptions import ValidationError

@login_required
def index(request):
    """
    List non-trashed questions filtered by the user.
    The question list is paginated.
    """
    filter_string = request.GET.get('filter')
    difficulty = request.GET.get('difficulty')
    question_type = request.GET.get('type')
    question_page = request.GET.get('question_page')

    questions = Question.objects.filter(trash=False)

    if filter_string:
        questions = questions.filter(
                Q(question__icontains=filter_string) |
                Q(created_by__username__icontains=filter_string) |
                Q(page__title__icontains=filter_string)
        )

    if difficulty:
        questions = questions.filter(difficulty=difficulty)

    if question_type:
        questions = questions.filter(question_type=question_type)

    if question_page:
        questions = questions.filter(
                page=get_object_or_404(Page, pk=question_page)
        )

    paginator = Paginator(questions, 10)
    page = request.GET.get('page')
    try:
        questions = paginator.page(page)
    except PageNotAnInteger:
        questions = paginator.page(1)
    except EmptyPage:
        questions = paginator.page(paginator.num_pages)

    difficulties = Difficulty.objects.all()
    question_types = QUESTION_TYPES
    question_pages = Page.objects.all()

    return render(request, 'index.html', 
            { 'difficulties': difficulties, 
              'question_types': question_types, 
              'question_pages': question_pages, 
              'questions': questions }
    )

@login_required
def show(request, question_id):
    """Show details about the specified question"""
    question = Question.objects.get(pk=question_id)
    correct_answers = Correct.objects.filter(question=question)
    wrong_answers = Wrong.objects.filter(question=question)
    return render(request, 'show.html', 
            { 'question': question, 'correct_answers': correct_answers, 
                'wrong_answers': wrong_answers })

@login_required
def new(request, test_id=None):
    """Create a new question"""
    # Default values for controls
    question = Question()
    answer_data = {
        'answer_count': None,
        'answers': [],
    }

    if request.method == 'POST':
        # Collect question parameters
        question.question = request.POST['question']

        if request.POST['question-difficulty']:
            question.difficulty = get_object_or_404(
                    Difficulty, pk=request.POST['question-difficulty']
            )

        if request.POST['question-page']: 
            question.page = get_object_or_404(
                    Page, pk=request.POST['question-page']
            )

        question.question_type = request.POST['question-type']

        if 'question-english' in request.POST:
            question.english = True
        else:
            question.english = False

        if 'question-case-sensitive' in request.POST:
            question.is_case_sensitive = True
        else:
            question.is_case_sensitive = False

        question.created_by = request.user

        question.correct_info = request.POST['question-correct-info']
        question.wrong_info = request.POST['question-wrong-info']

        # Get answer
        answer_data['answer_count'] = request.POST['answer-count']
        answer_data['answers'] = []

        answer_count = answer_data['answer_count']
        for i in range(int(answer_count)):
            is_correct_answer = False
            if ('answer-%d-is-correct-answer' % i) in request.POST:
                is_correct_answer = True
            answer = Answer(answer=request.POST['answer-%d-answer' % i])
            answer_data['answers'].append((is_correct_answer, answer))

        validation_error = False
        try:
            question.full_clean()
            # TODO: Make sure question_type is valid
        except ValidationError as e:
            validation_error = True
            messages.error(request, 'Question: ' + ''.join(e.messages))

        try:
            for _, answer in answer_data['answers']:
                answer.full_clean()
        except ValidationError as e:
            validation_error = True
            messages.error(request, 'Answer: ' + ''.join(e.messages))

        # If no errors then save models to database and redirect.
        if not validation_error:
            question.save()
            for is_correct_answer, answer in answer_data['answers']:
                answer.save()
                if is_correct_answer:
                    correct = Correct(question=question, answer=answer)
                    correct.save()
                else:
                    wrong = Wrong(question=question, answer=answer)
                    wrong.save()

            if test_id:
                test = get_object_or_404(Test, pk=test_id)
                contains = Contains(test=test, question=question)
                contains.save()
                return redirect(test)
            else:
                return redirect('question.views.index')

    pages = list(Page.objects.all())
    difficulties = list(Difficulty.objects.all())
    question_types = QUESTION_TYPES
    test = None
    if test_id:
        test = get_object_or_404(Test, pk=test_id)
        question.page = test

    return render(request, 'new.html',
            { 'question': question, 'answer_data': answer_data, 
              'difficulties': difficulties, 'question_types': question_types,
              'pages': pages, 'test': test }
    )

@login_required
def answer(request, question_type):
    """
    Returns requested QuestionType's partial url.
    If the requested question_type does not exist then
    single_select will be used as default.
    """
    if question_type in QUESTION_TYPES:
        return render(request, 
                QUESTION_TYPES.get(question_type).answer_partial_url)
    else:
        return render(request, 
                QUESTION_TYPES.get('single_select').answer_partial_url)

@login_required
def edit(request, question_id, test_id=None):
    """
    Edit an existing question and/or its associated answers.
    """
    # Question
    question = get_object_or_404(Question, pk=question_id)

    # Answers
    correct_answers = Correct.objects.filter(question=question)
    wrong_answers = Wrong.objects.filter(question=question)

    answers = list(chain(correct_answers, wrong_answers))

    answer_data = {
        'answer_count': 0,
        'answers': [],
    }

    for answer in answers:
        answer_data['answer_count'] += 1
        is_correct_answer = False
        if type(answer) is Correct:
            is_correct_answer = True
        answer_data['answers'].append((is_correct_answer, answer))

    if request.method == 'POST':
        # Check if any part of the question or its answer(s) changed before
        # updating the question in the database.
        question_changed = False
        answer_changed = False
        validation_error = False

        if not question.question == request.POST['question']:
            question.question = request.POST['question']
            question_changed = True

        if request.POST['question-difficulty']:
            difficulty = get_object_or_404(
                    Difficulty, pk=request.POST['question-difficulty']
            )
        else:
            difficulty = None
        if not question.difficulty == difficulty:
            question.difficulty = difficulty
            question_changed = True

        if request.POST['question-page']:
            page = get_object_or_404(
                    Page, pk=request.POST['question-page']
            )
            if not question.page == page:
                question.page = page
                question_changed = True

        if not question.question_type == request.POST['question-type']:
            question.question_type = question_type
            question_changed = True
            answer_changed = True

        is_in_english = False
        if 'question-english' in request.POST:
            is_in_english = True

        if not question.english == is_in_english:
            question.english = is_in_english
            question_changed = True
                
        case_sensitivity = False
        if 'question-case-sensitive' in request.POST:
            case_sensitivity = True

        if not question.is_case_sensitive == case_sensitivity:
            question.is_case_sensitive = case_sensitivity
            question_changed = True

        if not question.correct_info == request.POST['question-correct-info']:
            question.correct_info = request.POST['question-correct-info']
            question_changed = True

        if not question.wrong_info == request.POST['question-wrong-info']:
            question.wrong_info = request.POST['question-wrong-info']
            question_changed = True

        # Validate and save new question if it was changed
        if question_changed:
            try:
                question.full_clean()
            except ValidationError as e:
                validation_error = True
                messages.error(request, 'Question: ' + ''.join(e.messages))
        
        # Answer checking
        if not answer_changed: # If question_type changed answer will change
            answer_count = int(request.POST['answer-count'])
            if not answer_data['answer_count'] == answer_count:
                answer_data['answer_count'] = answer_count
                answer_changed = True
            else:
                for i in range(int(answer_count)):
                    match_found = False

                    is_correct_answer = False
                    if ('answer-%d-is-correct-answer' % i) in request.POST:
                        is_correct_answer = True
                    answer_text = request.POST['answer-%d-answer' % i]

                    for curr_is_corr_answer, answer in answer_data['answers']:
                        if (answer.answer.answer == answer_text and
                                (curr_is_corr_answer == is_correct_answer)):
                            match_found = True
                            break

                    if not match_found:
                        answer_changed = True
                        break

        # Validate and save new answers if they were changed
        if answer_changed:
            answer_data['answer_count'] = request.POST['answer-count']
            answer_data['answers'] = []

            for i in range(int(answer_data['answer_count'])):
                is_correct_answer = False
                if ('answer-%d-is-correct-answer' % i) in request.POST:
                    is_correct_answer = True
                answer = Answer(answer=request.POST['answer-%d-answer' % i])
                answer_data['answers'].append((is_correct_answer, answer))

            try:
                for _, answer in answer_data['answers']:
                    answer.full_clean()
            except ValidationError as e:
                validation_error = True
                messages.error(request, 'Answer: ' + ''.join(e.messages))

        # Save changes
        if not validation_error:

            question.modified_by = request.user
            question.save()

            if answer_changed:
                for is_correct_answer, answer in answer_data['answers']:
                    answer.save()
                    if is_correct_answer:
                        correct = Correct(question=question, answer=answer)
                        correct.save()
                    else:
                        wrong = Wrong(question=question, answer=answer)
                        wrong.save()

                # Delete old answers
                for answer in answers:
                    answer.answer.delete()

            if test_id:
                test = get_object_or_404(Test, pk=test_id)
                return redirect(test)
            else:
                return redirect('question.views.index')

    pages = list(Page.objects.all())
    difficulties = list(Difficulty.objects.all())
    question_types = QUESTION_TYPES
    test = None
    if test_id:
        test = get_object_or_404(Test, pk=test_id)

    return render(request, 'edit.html', 
            { 'question': question, 'answer_data': answer_data, 
              'difficulties': difficulties, 'question_types': question_types,
              'pages': pages, 'test': test }
    )

@login_required
def remove(request, question_id):
    """
    Remove an existing question and its associated answers.
    Effectively marks them as trashed, putting it in the bin.
    """
    question = get_object_or_404(Question, pk=question_id)
    question.trash = True
    question.save()
    return redirect('question.views.index')

@login_required
def delete(request, question_id):
    """
    Permanently delete an existing question and its associated answers from
    the database.
    """
    question = get_object_or_404(Question, pk=question_id)

    correct_answers = Correct.objects.filter(question=question)
    wrong_answers = Wrong.objects.filter(question=question)

    for answer in correct_answers:
        answer.answer.delete()

    for answer in wrong_answers:
        answer.answer.delete()

    question.delete()

    return redirect('trash.views.trash_questions')

@login_required
def index_difficulties(request):
    """List all difficulties."""
    difficulties = Difficulty.objects.all()
    return render(request, 'index_difficulties.html', 
            { 'difficulties': difficulties })

@login_required
def new_difficulty(request):
    """Create a new difficulty."""
    if request.method == 'POST':
        # TODO: Error handling and validation
        difficulty = Difficulty(name=request.POST['difficulty'])
        try:
            difficulty.full_clean()
            difficulty.save()
            return redirect('question.views.index_difficulties')
        except ValidationError as e:
            pass

    return render(request, 'new_difficulty.html')

@login_required
def delete_difficulty(request, difficulty_id):
    """
    Permanently delete this difficulty.
    """
    
    difficulty = get_object_or_404(Difficulty, pk=difficulty_id)

    questions = Question.objects.filter(difficulty=difficulty)
    for question in questions:
        question.difficulty = None
        question.save()

    difficulty.delete()

    return redirect('question.views.index_difficulties')

